package com.cloud.handler;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.cloud.Enum.ConsumerTypeEnum;
import com.cloud.entity.SysLog;
import com.cloud.service.LogSendFeignContain;
import com.cloud.util.BaseContextUtil;
import com.cloud.util.ClientUtil;
import io.netty.util.internal.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.apache.skywalking.apm.toolkit.trace.TraceContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Type;
import java.net.InetAddress;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.*;

/**
 * @ClassName: LogHandler
 * @Description: 捕捉请求和响应数据
 * @Version: v1.0.0
 * @Author: yangyuwei
 * @Date: 2020/9/25
 * Modification History:
 * Date             Author      Version     Description
 * -------------------------------------------------------------
 * 2020/9/25      yangyuwei      v1.0.0      创建
 */
@RestControllerAdvice
@Order(1)
@Slf4j
public class LogHandler extends HandlerInterceptorAdapter implements ResponseBodyAdvice, RequestBodyAdvice {

    @Autowired
    private RocketMQTemplate rocketMQTemplate;
    private static final String SYS_LOG = "log";

    /**************************请求头变量名*************************************/
    public static final String TOKEN_HEADER = "Authorization";
    public static final String ACCOUNT_ID = "accountId";
    public static final String CO_ID = "coId";
    public static final String USER_NAME = "userName";
    public static final String REMOTE_IP = "ip";
    public static final String TOKEN_TYPE = "type";
    public static final String APP_ID = "appid";
    public static final String TRACE_ID = "trace-id";
    public static final String SKY_TRACE_ID = "sky-trace-id";
    public static final String SPAN_ID = "span-id";
    public static final String PARENT_SPAN_ID = "parent-id";
    public static final String ROOT = "root";
    public static final String SOURCE_NAME = "source-name";
    //双中心的traceId，X-B3-TraceId
    public static final String DOUBLE_CENTER_TRACE_ID = "X-B3-TraceId";

    @Value("${spring.cloud.stream.bindings.logoutput.destination}")
    private String topicName;

    @Value("${cloud.joyoung.log.consumer-type:1}")
    private String consumerType;

//    @Autowired
//    private LogSend logSend;

    @Autowired
    LogSendFeignContain logSendFeignContain;

    @Value("${spring.application.name:default-application}")
    private String applicationName;

    @PostConstruct
    void check() throws Exception {
        if (StringUtil.isNullOrEmpty(topicName)) {
            throw new Exception("日志相关参数[spring.cloud.stream.bindings.logoutput.destination]未配置!");
        }
    }

    /**
     * @param request  description
     * @param response description
     * @param handler  description
     * @return boolean
     * @Description 在此拦截请求参数
     * @author yangyuwei on 2020/9/25 20:54
     **/
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        String contentType = request.getContentType();
        String tranceId = request.getHeader(TRACE_ID);
        //获取默认的traceID，没有的话，尝试获取双中心的
        if (StringUtils.isBlank(tranceId)) {
            tranceId = request.getHeader(DOUBLE_CENTER_TRACE_ID);
        }
        String spanId = UUID.randomUUID().toString().replace("-", "");
        tranceId = StringUtil.isNullOrEmpty(tranceId) ? spanId : tranceId;
        String parentId = request.getHeader(PARENT_SPAN_ID);
        parentId = StringUtil.isNullOrEmpty(parentId) ? ROOT : parentId;
        String ip = request.getHeader(REMOTE_IP);
        ip = StringUtil.isNullOrEmpty(ip) ? ClientUtil.getClientIp() : ip;
        BaseContextUtil.set(TRACE_ID, tranceId);
        BaseContextUtil.set(SPAN_ID, spanId);
        String username = null;
        String acctType = null;
        String customEncode = request.getHeader("custom");
        if (!StringUtil.isNullOrEmpty(customEncode)) {
            String customdecode = new String(Base64.getDecoder().decode(customEncode), Charset.forName("UTF-8"));
            JSONObject subjectInfo = JSONObject.parseObject(customdecode);
            acctType = subjectInfo.getString("type");
            username = subjectInfo.containsKey("userName") ? subjectInfo.getString("userName") : subjectInfo.getString("appid");
        }
        //获取调用链ID
        String skyTraceId = TraceContext.traceId();
        BaseContextUtil.set(SKY_TRACE_ID, skyTraceId);
        String sourceApplicationName = null;
        // 获取请求头
        Enumeration<String> enumeration = request.getHeaderNames();
        StringBuffer headers = new StringBuffer();
        while (enumeration.hasMoreElements()) {
            String name = enumeration.nextElement();
            String value = request.getHeader(name);
            headers.append(name + ":" + value).append(",");
            if (SOURCE_NAME.equals(name) && StringUtils.isNotBlank(value)) {
                sourceApplicationName = value;
            }
        }
        SysLog logger = new SysLog()
                .setId(spanId)
                .setSkyTraceId(skyTraceId)
                .setSourceApplicationName(sourceApplicationName)
                .setReqUser(username)
                .setReqTime(new Date())
                .setReqAddr(request.getRequestURI())
                .setReqHost(ip)
                .setReqMethod(request.getMethod())
                .setApplicationName(applicationName)
                .setAcctType(acctType)
                .setSpanId(spanId)
                .setTraceId(tranceId)
                .setParentId(parentId);
        if (contentType != null && MediaType.APPLICATION_FORM_URLENCODED_VALUE.contains(contentType)) {
            logger.setReqParam(JSONObject.toJSONString(request.getParameterMap()));
        } else if ("GET".equals(request.getMethod())) {
            logger.setReqParam(JSONObject.toJSONString(request.getParameterMap()));
        }
        BaseContextUtil.set(SYS_LOG, logger);
        return true;
    }


    /**
     * @param methodParameter description
     * @param targetType      description
     * @param converterType   description
     * @return boolean
     * @Description 是否拦截请求体
     * @author yangyuwei on 2020/9/27 17:16
     **/
    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
        return inputMessage;
    }

    /**
     * @param body          description
     * @param inputMessage  description
     * @param parameter     description
     * @param targetType    description
     * @param converterType description
     * @return java.lang.Object
     * @Description 获取请求体
     * @author yangyuwei on 2020/9/27 17:16
     **/
    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        SysLog logger = BaseContextUtil.get(SYS_LOG);
        if (logger != null) {
            if (body instanceof String) {
                logger.setReqParam((String) body);
            } else {
                logger.setReqParam(JSONObject.toJSONString(body));
            }
        }
        return body;
    }

    /**
     * @param body          description
     * @param inputMessage  description
     * @param parameter     description
     * @param targetType    description
     * @param converterType description
     * @return java.lang.Object
     * @Description 请求体为空
     * @author yangyuwei on 2020/9/27 17:18
     **/
    @Override
    public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        SysLog logger = BaseContextUtil.get(SYS_LOG);
        if (logger != null) {
            logger.setReqParam("");
        }
        return body;
    }

    /**
     * @param methodParameter description
     * @param aClass          description
     * @return boolean
     * @Description 是否支持此响应
     * @author yangyuwei on 2020/9/25 20:06
     **/
    @Override
    public boolean supports(MethodParameter methodParameter, Class aClass) {
        return true;
    }

    /**
     * @param o                  description
     * @param methodParameter    description
     * @param mediaType          description
     * @param aClass             description
     * @param serverHttpRequest  description
     * @param serverHttpResponse description
     * @return java.lang.Object
     * @Description 在响应被转化前调用此接口, 并使syslog成为完全体
     * @author yangyuwei on 2020/9/25 20:07
     **/
    @Override
    public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        try {
            SysLog logger = BaseContextUtil.get(SYS_LOG);
            if (logger == null) {
                logger = this.createSysLog(serverHttpRequest);
            }
            logger.setRespHeader(serverHttpResponse.getHeaders().toString());
            if (o instanceof Exception) {
                //异常时 将状态码记录为500
                Exception ex = (Exception) o;
                logger.setRespCode(500);
            } else if (o instanceof JSONArray) {
                //响应值为jsonarray数据,无法判断状态,将状态码记录为300
                logger.setRespCode(300);
            } else {
                JSONObject resp = null;
                if (o instanceof JSONObject) {
                    resp = (JSONObject) o;
                } else {
                    Object o1 = JSON.toJSON(o);
                    if (o1 instanceof JSONObject) {
                        resp = (JSONObject) o1;
                    } else {
                        //响应值无法解析为jsonobject,无法判断状态,将状态码记录为300
                        logger.setRespCode(300);
                    }
                    if (resp != null) {
                        if (resp.containsKey("code")) {
                            if ("200".equals(resp.getString("code"))
                                    || "0".equals(resp.getString("code"))
                                    || "success".equalsIgnoreCase(resp.getString("code"))) {
                                logger.setRespCode(200);
                            } else {
                                logger.setRespCode(500);
                            }
                        } else {
                            logger.setRespCode(300);//无法解析
                        }
                    }
                }
            }
            logger.setReqHeader(serverHttpRequest.getHeaders().toString());
            if (Objects.nonNull(o)) {
                logger.setRespBody(JSON.toJSONString(o));
            }
            logger.setRespTime(new Date());
            logger.setProcessTime(logger.getRespTime().getTime() - logger.getReqTime().getTime());
            InetAddress ia = InetAddress.getLocalHost();
            logger.setInstance(ia.toString());
            log.debug("日志拦截其发送日志：" + JSON.toJSONString(logger));
            if (ConsumerTypeEnum.ROCKETMQ.getCode().equals(consumerType)) {
                sendLog(logger);
            } else if (ConsumerTypeEnum.COMMON_FEIGN.getCode().equals(consumerType)
                    || ConsumerTypeEnum.BRIDGE_FEIGN.getCode().equals(consumerType)
                    || ConsumerTypeEnum.HTTPCLIENT.getCode().equals(consumerType)) {
                logSendFeignContain.sendLog(logger);
            }
        } catch (Exception e) {
            log.error("日志采集失败：" + e);
        }
        return o;
    }

    private boolean isDig(String code) {
        return true;
    }

    /**
     * @param logger description
     * @return void
     * @Description 发送消息
     * @author yangyuwei on 2020/9/25 19:43
     **/
    private void sendLog(SysLog logger) {

//        MessageBuilder<SysLog> builder = MessageBuilder.withPayload(logger);
//        Message<SysLog> message = builder.build();
//        boolean sendresult = logSend.logoutput().send(message);
        rocketMQTemplate.convertAndSend("cloud_log", logger);
    }

    /**
     * @param serverHttpRequest
     * @return SysLog 日志对象
     * @Description 创建SysLog
     **/
    private SysLog createSysLog(ServerHttpRequest serverHttpRequest) {
        //获取请求头
        HttpHeaders headers = serverHttpRequest.getHeaders();
        //获取来源项目名称
        String sourceName = null;
        if (!CollectionUtils.isEmpty(headers.get(SOURCE_NAME))) {
            sourceName = headers.get(SOURCE_NAME).get(0);
        }
        //获取spanId
        String spanId = UUID.randomUUID().toString().replace("-", "");
        //获取traceId
        String traceId = null;
        if (!CollectionUtils.isEmpty(headers.get(TRACE_ID))) {
            traceId = headers.get(TRACE_ID).get(0);
        }
        if (StringUtils.isBlank(traceId) && !CollectionUtils.isEmpty(headers.get(DOUBLE_CENTER_TRACE_ID))) {
            traceId = headers.get(DOUBLE_CENTER_TRACE_ID).get(0);
        }
        if (StringUtils.isBlank(traceId) && StringUtils.isNotEmpty(BaseContextUtil.get(TRACE_ID))) {
            traceId = BaseContextUtil.get(TRACE_ID);
        }
        if (StringUtils.isBlank(traceId)) {
            traceId = spanId;
        }
        //获取parentId
        String parentId = CollectionUtils.isEmpty(headers.get(PARENT_SPAN_ID))
                ? "ROOT" : headers.get(PARENT_SPAN_ID).get(0);
        URI uri = serverHttpRequest.getURI();
        SysLog logger = new SysLog()
                .setId(spanId)
                .setSkyTraceId(BaseContextUtil.get(SKY_TRACE_ID))
                .setSourceApplicationName(sourceName)
                .setReqTime(new Date())
                .setReqAddr(uri.getPath())
                .setReqHost(uri.getAuthority())
                .setReqMethod(serverHttpRequest.getMethod().name())
                .setApplicationName(applicationName)
                .setSpanId(spanId)
                .setTraceId(traceId)
                .setParentId(parentId);
        return logger;
    }
}